﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;

namespace CommonUtils
{
    /// <summary>
    /// Socket相关函数
    /// </summary>
    public static class SocketUtil
    {
        /// <summary>
        /// 默认没有结束字符
        /// 如果不使用结束字符，就假定消息非常短，收到bytes-block直接回调
        /// 能用WebSocket就不要用Socket
        /// </summary>
        public static bool HasEnd = false;

        /// <summary>
        /// 结束标识 尽量短 保证不拆包
        /// </summary>
        public const string EndRemark = "!end!";

        /// <summary>
        /// 结束标识 尽量短 保证不拆包
        /// </summary>
        private static byte[] _endBytes = StringUtil.GetBytes("!end!");

        public static void SendEnd(this Socket socket)
        => socket.Send(_endBytes);

        /// <summary>
        /// 不要使用原生的Send(bytes),因为未封装结束字符
        /// </summary>
        public static void SendBytes(this Socket socket, byte[] bytes)
        {
            socket.Send(bytes);

            if (HasEnd)
                socket.SendEnd();
        }

        public static void Send(this Socket socket, string msg)
        => socket.SendBytes(msg.GetBytes());

        public static void Send(this Socket socket, Stream stream, bool closeStream = true)
        => socket.SendBytes(stream.GetBytes(closeStream));

        /// <summary>
        /// 不要使用原生的SendFile,因为未封装结束字符
        /// </summary>
        public static void SendFileEx(this Socket socket, string path)
        {
            socket.SendFile(path);

            if (HasEnd)
                socket.SendEnd();
        }

        /// <summary>
        /// 单次发送/接收数据长度
        /// </summary>
        private const int _packageSize = 10240;//10KB

        public static byte[][] ReceiveBytesArray(this Socket socket)
        {
            var bytes = new List<byte>();
            while (true)
            {
                var buffer = new byte[_packageSize];
                var length = socket.Receive(buffer);
                if (length == 0)
                    throw (new Exception("收到长度为0的字节组，远端会话请求断开"));

                buffer = buffer.Take(length).ToArray();

                if (!HasEnd)
                    return new byte[][] { buffer };

                LogUtil.PrintInfo("Socket {0} has received bytes with length {1} from {2}.[{3}]", socket.LocalEndPoint, buffer.Length, socket.RemoteEndPoint, (char)buffer[0]);
                bytes.AddRange(buffer);//组包

                //一直收不到结束字符可能造成内存溢出崩溃
                if (bytes.EqualsEnd(_endBytes))
                    return bytes.ToArray().SplitBy(_endBytes);//组包完成
            }
        }

        /// <summary>
        /// 优化关闭
        /// </summary>
        public static void DisconnectCloseDispose(this Socket socket)
        {
            if (socket.Connected)
                socket.Disconnect(false);

            socket.Close();
            socket.Dispose();
        }

        /// <summary>
        /// 是否绑定了端口
        /// </summary>
        public static bool IsBind(this Socket socket)
        {
            try
            {
                return socket.LocalEndPoint != null;
            }
            catch
            {
                return false;
            }
        }

        public static bool CanConnect(string host, int port)
        {
            var result = new SocketClient(host, port).CanConnect();
            LogUtil.Write("Try to telnet {0} {1} limit 1 ms, result: {2}", host, port, result);
            return result;
        }

    }
}